#include <objbase.h>
#include "cfileio.hpp"
#include <cassert>

CFileIO::CFileIO() : m_hFile(INVALID_HANDLE_VALUE)
{
}


CFileIO::~CFileIO()
{
    const HRESULT hr = Close();
    hr;
    assert(SUCCEEDED(hr));
}


HRESULT CFileIO::Open(
    const wchar_t* strFileName,
    DWORD dwDesiredAccess,
    DWORD dwShareMode,
    DWORD dwCreationDisposition,
    DWORD dwFlagsAndAttributes)
{
    if (strFileName == 0)
        return E_INVALIDARG;
        
    if (m_hFile != INVALID_HANDLE_VALUE)
        return E_UNEXPECTED;
        
    m_hFile = CreateFile(
                strFileName,
                dwDesiredAccess,
                dwShareMode,
                0,  //security attributes
                dwCreationDisposition,
                dwFlagsAndAttributes,
                0);
                
    const DWORD e = GetLastError();

    if (m_hFile == INVALID_HANDLE_VALUE)
        return HRESULT_FROM_WIN32(e);
        
    if (e == ERROR_ALREADY_EXISTS)
        return S_FALSE;
        
    assert(e == 0);
    return S_OK;
}


HRESULT CFileIO::Close()
{
    if (m_hFile == INVALID_HANDLE_VALUE)
        return S_FALSE;
        
    const BOOL b = CloseHandle(m_hFile);
    
    m_hFile = INVALID_HANDLE_VALUE;
    
    if (b)
        return S_OK;
        
    const DWORD e = GetLastError();
    return HRESULT_FROM_WIN32(e);
}


HRESULT CFileIO::QueryInterface(const IID& iid, void** ppv)
{
    if (ppv == 0)
        return E_INVALIDARG;
        
    IUnknown*& pUnk = reinterpret_cast<IUnknown*&>(*ppv);
    
    if (iid == __uuidof(IUnknown))
        pUnk = static_cast<IUnknown*>(this);
        
    else if (iid == __uuidof(ISequentialStream))
        pUnk = static_cast<IUnknown*>(this);
        
    else if (iid == __uuidof(IStream))
        pUnk = static_cast<IStream*>(this);
        
    else
    {
        pUnk = 0;
        return E_NOINTERFACE;
    }
    
    pUnk->AddRef();
    return S_OK;
}


ULONG CFileIO::AddRef()
{
    return 2;
}


ULONG CFileIO::Release()
{
    return 1;
}


HRESULT CFileIO::Read(void* pv, ULONG cb, ULONG* pcb)
{
    const BOOL b = ReadFile(m_hFile, pv, cb, pcb, 0);
    
    if (b)
        return S_OK;
        
    const DWORD e = GetLastError();
    return HRESULT_FROM_WIN32(e);
}

    
HRESULT CFileIO::Write(const void* pv, ULONG cb, ULONG* pcb)
{
    const BOOL b = WriteFile(m_hFile, pv, cb, pcb, 0);
    
    if (b)
        return S_OK;
        
    const DWORD e = GetLastError();
    return HRESULT_FROM_WIN32(e);
}


HRESULT CFileIO::Seek( 
    LARGE_INTEGER pos,
    DWORD dwMove_,
    ULARGE_INTEGER* pnewpos)
{
    DWORD dwMove;
    
    switch (dwMove_)
    {
        case STREAM_SEEK_SET:
            dwMove = FILE_BEGIN;
            break;
            
        case STREAM_SEEK_CUR:
            dwMove = FILE_CURRENT;
            break;
            
        case STREAM_SEEK_END:
            dwMove = FILE_END;
            break;
            
        default:
            return E_INVALIDARG;
    }
    
    const BOOL b = SetFilePointerEx(
                    m_hFile, 
                    pos, 
                    PLARGE_INTEGER(pnewpos), 
                    dwMove);
    
    if (b)
        return S_OK;
        
    const DWORD e = GetLastError();
    return HRESULT_FROM_WIN32(e);
}


HRESULT CFileIO::SetSize(ULARGE_INTEGER pos_)
{
    const LARGE_INTEGER zero = { 0 };
    
    ULARGE_INTEGER curpos_;
    
    HRESULT hr = Seek(zero, STREAM_SEEK_CUR, &curpos_);
    
    if (FAILED(hr))
        return hr;
        
    const LARGE_INTEGER& pos =
        reinterpret_cast<const LARGE_INTEGER&>(pos_);
        
    hr = Seek(pos, STREAM_SEEK_SET, 0);
    
    if (FAILED(hr))
        return hr;
         
    const BOOL b = SetEndOfFile(m_hFile);
    
    if (!b)
    {
        const DWORD e = GetLastError();
        return HRESULT_FROM_WIN32(e);
    }
    
    if (curpos_.QuadPart >= pos_.QuadPart)  //truncation
        return S_OK;
        
    const LARGE_INTEGER& curpos =
        reinterpret_cast<const LARGE_INTEGER&>(curpos_);
    
    hr = Seek(curpos, STREAM_SEEK_SET, 0);
    
    if (FAILED(hr))
    {
        const DWORD e = GetLastError();
        return HRESULT_FROM_WIN32(e);
    }
    
    return S_OK;
}


#if 0
HRESULT STDMETHODCALLTYPE CopyTo( 
    IStream*,
    ULARGE_INTEGER,
    ULARGE_INTEGER*,
    ULARGE_INTEGER*);

HRESULT STDMETHODCALLTYPE Commit(DWORD);

HRESULT STDMETHODCALLTYPE Revert();

HRESULT STDMETHODCALLTYPE LockRegion( 
    ULARGE_INTEGER,
    ULARGE_INTEGER,
    DWORD);

HRESULT STDMETHODCALLTYPE UnlockRegion( 
    ULARGE_INTEGER,
    ULARGE_INTEGER,
    DWORD);

HRESULT STDMETHODCALLTYPE Stat(STATSTG*, DWORD);

HRESULT STDMETHODCALLTYPE Clone(IStream**);
    
#endif
